Great Expectationsを用いたデータ品質テストがdbt上で行えるpackage「dbt_expectations」を試してみた #dbt
さがらです。
Great Expectationsを用いたデータ品質テストがdbt上で行えるpackage「dbt_expectations」を試してみたので、その内容をまとめてみます。
dbt_expectationsとは
dbt_expectationsに関する情報は、下記ページにまとまっております。
このページの説明を見ると、このように書いてあります。
dbt-expectations is an extension package for dbt, inspired by the Great Expectations package for Python. The intent is to allow dbt users to deploy GE-like tests in their data warehouse directly from dbt, vs having to add another integration with their data warehouse.
記事タイトルにもありますが、Great Expectationsというデータ品質のためにテスト・ドキュメント化・プロファイリングが行えるOSSがあり、その機能をdbtから直接実行できるというdbt packageになっております。
元のOSSであるGreat Expectationsに関しては、dbtやAirflowと組み合わせた事例がすでに存在しており、日本語でもいくつか検証記事が投稿されています。こちらもぜひ御覧ください。
標準のtestやdbt_utilsのtestと何が違うの?
dbtには標準でテスト機能が備わっており、別packageのdbt_utils
にもGeneric Testがいくつか含まれています。
そのため、このdbt_expectations
は何が違うのか、気になる方も多いと思います。
ざっくりではありますが、以下に2つ違いを記しておきます。
packageに含まれるテストの数が多い
まず、dbt_expectations
に含まれるテストの数が多いです。ver0.5.8時点で、なんと60個のテストが用意されています。
ver0.8.6のdbt_utils
に含まれるテストは15個のため、その数なんと4倍です。
dbtでは自分でGeneric Testを定義することも出来ますが、packageに該当するものがあればそれを使ってしまう方が楽だと思います。
そのため、dbt_utils
にも含まれないテストを探している場合には、dbt_expectations
から探してみましょう。
統計値を参照するテスト、など特有のテストがある
dbt_expectations
には60個のテストがあると上述しましたが、テストの種類としても多くの種類のテストがあります。
以下、dbt_expectationsのドキュメント上の分類ですが、このように分けられます。(各項目に記した”●個”は、その分類の中でいくつテストがあるかを意味しています。)
- Table shape:14個
- 保持するカラムやレコード数など、テーブルの形を確認するテスト
- Missing values, unique values, and types:6個
- 対象のカラムの値について、NULL、ユニーク、型、などを確認するテスト
- Sets and ranges:5個
- 対象のカラムの値が、指定した範囲やリストに含まれるかを確認するテスト
- String matching:10個
- 対象のカラムの値が、指定した長さ・パターン・正規表現に一致するか、を確認するテスト
- Aggregate functions:16個
- 対象のカラムに対して、重複削除した際の値や、最大値や中央値などの統計値を用いて指定した条件をクリアするか確認するテスト
- Multi-column:6個
- 複数のカラム間で値を比較して、指定した条件をクリアするか確認するテスト
- Distributional functions:3個
- 標準偏差を取り指定した範囲内に収まっているか、を確認するテスト
- 指定した条件に合致する日時に関するレコードがあるか、を確認するテスト
dbt標準・dbt_utils・dbt_expectations、各テストをどう使い分けるか?
前述した通り、dbtには「dbt標準のテスト」「dbt_utils
を用いたテスト」「dbt_expectations
を用いたテスト」3種類あると述べました。
それぞれどう使い分ければよいのか悩むと思うのですが、私はこちらのFLYWHEEL社の記事の”基本方針”に記載の考え方に全面的に同意です。(FLYWHEEL社ではData Orchestrationツール上で、OSSのGreat Expectationsを実行しているという違いはありますが。)
dbt は SQL3を開発しながら繰り返し実行されるようなテストを記述し、Great Expectations には本番データを対象に Data Orchestration ツール (Dagster, Airflow 等) を介して実行されるテストを記述する。
dbtで行うことができるデータのテストとしては、大きく2種類あると思います。「dbtで開発した処理のテスト」「本番稼働中のデータ基盤で異常値がないかを確認するテスト」の2種類です。
それぞれ、こういった違いがあると思います。
- dbtで開発した処理のテスト
- 処理が上手く開発できていないと、本来uniqueであるべきカラムがuniqueでなくなったり、処理前と処理後のテーブルでレコード数が一致しなかったり、といったことが起こりえます。
- この処理をテストするには、dbt標準の
unique
テストや、dbt_utils
のequal_rowcount
テストが向いていると思います。
- 本番稼働中のデータ基盤で異常値がないかを確認するテスト
- 本番稼働中のデータ基盤では、ソースデータがハンド入力のため異常値が入ってきたり、区切り文字の関係でロードすべきカラムの位置がずれたり、といったことが起こりえます。
- こういったデータの異常値を確認するには、
dbt_expectations
の統計値や集計値を用いたり、カラム間の値の関係性を用いたテストが向いていると思います。
また、dbtではtagを使って、dbt test
実行時にチェックするテストを切り分ける事ができます。そのため、開発時に行うdbt test
と、本番稼働中のデータ基盤で行うdbt test
を分けることができますので、不要なテストの実行を避けることができます。
もちろん例外的なパターンはあるかと思いますが、1つの考え方として参考になると嬉しいです。
dbt_expectationsのインストール
ここからは、実際にdbt_expectations
を試していきます!まずはインストールからやってみます。
環境
- dbt Cloud:v1.1.57.28
- dbt version:1.0 ※Environmentsから指定
- dbt_expectations:0.5.8
インストール
packages.yml
に、下記のように書いて保存した上で、dbt deps
コマンドを実行するとインストール出来ます。
packages: - package: calogica/dbt_expectations version: 0.5.8
ちなみに、dbt_expectations
はdbt_utils
とdbt_date
を使用しているので、packages.yml
にこれらのパッケージ名を書いていなくてもインストールされます。
dbt_date用のタイムゾーン指定
先程、dbt_expectations
をインストールするとdbt_date
もインストールされると書きましたが、dbt_date
向けにタイムゾーンを指定する必要があります。
デフォルトではAmerica/Los_Angeles
が指定されているので、ほぼ全ての方は設定を変更するべき内容です。基本的には、使用するデータウェアハウス上のタイムゾーンに併せたタイムゾーンを設定することになると思います。
タイムゾーンの指定方法としては、dbt_project.yml
の中で、dbt_date:time_zone
という変数を定義する必要があります。
日本の方は、UTC、日本時間(UTC+09:00)、のどちらかを使うことになると思いますので、以下変数の定義方法を記しておきます。
- UTCの場合
vars: 'dbt_date:time_zone': 'UTC'
- 日本時間(UTC+09:00)の場合
vars: 'dbt_date:time_zone': 'Asia/Tokyo'
「expect_column_values_to_match_like_pattern_list」を試す
続いて、実際にdbt_expectations
で使用できるテストを2つ試してみます。
1つ目として、expect_column_values_to_match_like_pattern_list
を試してみます。
このテストは、SQLのLIKE
句で使用するパターンをいくつかlistとして定義することで、そのlistの定義いずれかに合致するかを確認することが出来るテストです。
定義の仕方
カラムのdescription
などを定義するyaml上で、以下の様にtests
を定義します。
tests: - dbt_expectations.expect_column_values_to_match_like_pattern_list: like_pattern_list: ["%@%", "%&%"] match_on: any # (Optional. Default is 'any', which applies an 'OR' for each pattern. If 'all', it applies an 'AND' for each regex.) row_condition: "id is not null" # (Optional)
like_pattern_list
:SQLのLIKE句と同じ様に、一致確認させるパターンを記します。カンマ区切りで複数のパターンを併記することもできますmatch_on
:指定したオプションの方法で、テストを行います。any
の場合:like_pattern_list
に記載した1パターンに合致すればクリアall
の場合:like_pattern_list
に記載したパターン全てに合致すればクリア
row_condition
:~~ is not null
のように記述することで、対象のカラムを用いたWHERE句をテストに追記してくれます。(下図参照)
テスト用のデータ
このテスト用に、OKサンプルとNGサンプルをdbtのseed機能で定義しておきます。テストでは、「supplier_prefectures
列の値の末尾が”都道府県”いずれかで終わっているか」を確認します。
- OKサンプル:like_pattern_test_ok_example.csv
toy_id,supplier_prefectures 001,北海道 002,青森県 003,東京都 004,京都府
- NGサンプル:like_pattern_test_ng_example.csv
toy_id,supplier_prefectures 001,北海道 002,青森県青森市 003,東京都千代田区 004,京都府
テストの定義
下記のように、OKサンプル、NGサンプル、それぞれでtests
を定義しておきます。
※tags
は自分が検証時に区分けしやすいように付与しただけです。
seeds: - name: like_pattern_test_ok_example columns: - name: supplier_prefectures tests: - dbt_expectations.expect_column_values_to_match_like_pattern_list: like_pattern_list: ["%都", "%道", "%府", "%県"] match_on: any row_condition: "supplier_prefectures is not null" tags: ['like_pattern_ok'] - name: like_pattern_test_ng_example columns: - name: supplier_prefectures tests: - dbt_expectations.expect_column_values_to_match_like_pattern_list: like_pattern_list: ["%都", "%道", "%府", "%県"] match_on: any row_condition: "supplier_prefectures is not null" tags: ['like_pattern_ng']
テストの実行
- OKの場合
dbt test --select tag:like_pattern_ok
というコマンドを実行します。
「対象のテストがPASSしたよ」と表示されていますね。
- NGの場合
dbt test --select tag:like_pattern_ng
というコマンドを実行します。
テストに失敗すると、下図のようにエラーとなります。少し小さいのですが、赤枠内のFAIL 2
といった表記で、エラーを起こしている行数も返してくれます。
「expect_column_pair_values_A_to_be_greater_than_B」を試す
2つ目として、expect_column_pair_values_A_to_be_greater_than_B
を試してみます。
このテストは、「あるモデル上でA列の値の方がB列より常に大きい」ということを確認するテストです。
定義の仕方
モデルのdescription
などを定義するyaml上で、以下の様にtests
を定義します。
tests: - dbt_expectations.expect_column_pair_values_A_to_be_greater_than_B: column_A: col_numeric_a column_B: col_numeric_a or_equal: True row_condition: "id is not null" # (Optional)
column_A
:column_B
との比較対象のカラム名を入れます。※値が大きい方を選択column_B
:column_A
との比較対象のカラム名を入れます。※値が小さい方を選択or_equal
:True
の場合は「>=」、False
の場合は「>」で比較します。row_condition
:~~ is not null
のように記述することで、対象のカラムを用いたWHERE句をテストに追記してくれます。(下図参照)
テスト用のデータ
このテスト用に、OKサンプルとNGサンプルをdbtのseed機能で定義しておきます。テストでは、「sale_price
列の値が、purchase_price
列の値よりも常に大きいか」を確認します。
- OKサンプル:a_greater_than_b_test_ok_example.csv
toy_id,sale_price,purchase_price 001,200,100 002,300,200
- NGサンプル:a_greater_than_b_test_ng_example.csv
toy_id,sale_price,purchase_price 001,200,100 002,200,300
テストの定義
下記のように、OKサンプル、NGサンプル、それぞれでtests
を定義しておきます。
※tags
は自分が検証時に区分けしやすいように付与しただけです。
seeds: - name: a_greater_than_b_test_ok_example tests: - dbt_expectations.expect_column_pair_values_A_to_be_greater_than_B: column_A: sale_price column_B: purchase_price or_equal: True tags: ['greater_than_ok'] - name: a_greater_than_b_test_ng_example tests: - dbt_expectations.expect_column_pair_values_A_to_be_greater_than_B: column_A: sale_price column_B: purchase_price or_equal: True tags: ['greater_than_ng']
テストの実行
- OKの場合
dbt test --select tag:greater_than_ok
というコマンドを実行します。
「対象のテストがPASSしたよ」と表示されていますね。
- NGの場合
dbt test --select tag:greater_than_ng
というコマンドを実行します。
下図のようにエラーメッセージが返ってきました。FAIL 1
となっているので、「1行だけテストがFAILだったよ」ということを表しています。
生成されるドキュメントの確認
dbt_expectations
の元となっているGreat Expectationsはテストの定義に沿って、自動でその内容をドキュメント化する機能があります。(下図のようなイメージ、Great Expectations公式サイトからの抜粋です。)
一度dbt docs generate
を実行して、ドキュメントがどうなっているかを確認してみます。
今回はSeedを対象としたので、対象のSeedをドキュメント上で確認してみます。
Referenced Byという項目に、Testsが記載されていますね。
しかし、このTestsからリンクした先でもテスト内容のドキュメント化はされていませんでした。このドキュメント化も対応してくれると本家のGreat Expectationsにより近づくので、今後のアップデートに期待しています!
最後に
Great Expectationsを用いたデータ品質テストがdbt上で行えるpackage、dbt_expectations
を試してみました。
記事中でも述べた通り、テストの種類が豊富で、かつ実際のデータの統計値や集計値などを用いて、予想される(expectされる)値を持つか、テストを行えることがdbt_expectations
のテストの強みだと思います。
データ基盤上に異常値があるかどうかをdbtでテストしたい方には非常にオススメのpackageですので、ぜひ一度使ってみてください!